// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Linq;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;

namespace Taimila.Entify
{
	/// <summary>
	/// Contains extension methods for Entity type. These methods extend
	/// Entity type for framework usage. These methods cannot be used
	/// from the client application.
	/// </summary>
	internal static class EntityExtensions
	{
		/// <summary>
		/// Get entity identifier.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <returns>
		/// Entity identifier.
		/// </returns>
		internal static long? GetEntityId(this Entity entity)
		{
			FieldInfo field = typeof(Entity).GetField("metadata", BindingFlags.NonPublic | BindingFlags.Instance);
			EntityMetadata metadata = (EntityMetadata) field.GetValue(entity);
			return metadata.EntityId;
		}
		
		/// <summary>
		/// Set entity identifier.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <param name="entityId">
		/// Enity identifier.
		/// </param>
		internal static void SetEntityId(this Entity entity, long? entityId)
		{
			FieldInfo field = typeof(Entity).GetField("metadata", BindingFlags.NonPublic | BindingFlags.Instance);
			EntityMetadata metadata = (EntityMetadata) field.GetValue(entity);
			metadata.EntityId = entityId;
		}
		
		/// <summary>
		/// Get last modified date of the entity. This date is updated each
		/// time entity is stored to persistent store. In-memory changes doesn't
		/// update last modified date.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <returns>
		/// Last modified date of the entity <see cref="System.DateTime"/>
		/// </returns>
		internal static DateTime? GetLastModifiedDate(this Entity entity)
		{
			FieldInfo field = typeof(Entity).GetField("metadata", BindingFlags.NonPublic | BindingFlags.Instance);
			EntityMetadata metadata = (EntityMetadata) field.GetValue(entity);
			return metadata.Modified;
		}
		
		/// <summary>
		/// Get last modified date of the entity. This date is updated each
		/// time entity is stored to persistent store. In-memory changes doesn't
		/// update last modified date. This method returns timestamp in filetime
		/// format.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <returns>
		/// Last modified date of the entity in file time format. <see cref="System.DateTime"/>
		/// </returns>
		internal static long? GetLastModifiedDateAsFileTime(this Entity entity)
		{
			FieldInfo field = typeof(Entity).GetField("metadata", BindingFlags.NonPublic | BindingFlags.Instance);
			EntityMetadata metadata = (EntityMetadata) field.GetValue(entity);
			
			if(metadata.Modified == null)
			{
				return null;
			}
			else
			{
				return metadata.Modified.Value.ToFileTime();
			}
		}
		
		/// <summary>
		/// Set last modified date to the entity.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <param name="lastModified">
		/// New last modified date. <see cref="DateTime"/>
		/// </param>
		internal static void SetLastModifiedDate(this Entity entity, DateTime? lastModified)
		{
			FieldInfo field = typeof(Entity).GetField("metadata", BindingFlags.NonPublic | BindingFlags.Instance);
			EntityMetadata metadata = (EntityMetadata) field.GetValue(entity);
			metadata.Modified = lastModified;
		}
		
		/// <summary>
		/// Get source context of the entity. Source context is metadata
		/// information used by lazy laod operations.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <returns>
		/// Entity context where entity was fetched from. <see cref="EntityContext"/>
		/// </returns>
		internal static EntityContext GetSourceContext(this Entity entity)
		{
			FieldInfo field = typeof(Entity).GetField("metadata", BindingFlags.NonPublic | BindingFlags.Instance);
			EntityMetadata metadata = (EntityMetadata) field.GetValue(entity);
			return metadata.sourceContext;
		}
		
		/// <summary>
		/// Set source context to the entity. Source context is metadata
		/// information used by lazy laod operations.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <param name="context">
		/// Entity context where entity was fetched from. <see cref="EntityContext"/>
		/// </param>
		internal static void SetSourceContext(this Entity entity, EntityContext context)
		{
			FieldInfo field = typeof(Entity).GetField("metadata", BindingFlags.NonPublic | BindingFlags.Instance);
			EntityMetadata metadata = (EntityMetadata) field.GetValue(entity);
			metadata.sourceContext = context;
		}
		
		/// <summary>
		/// Get all identifiers of the related entities of the given many-to-many relation property. Skips
		/// possible null values.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <param name="propertyInfo">
		/// Many-to-many relation property. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// List of related entity identifiers.
		/// </returns>
		internal static List<long?> GetRelationIdentifiersOf(this Entity entity, PropertyInfo propertyInfo)
		{
			IList entities = (IList) propertyInfo.GetValue(entity, null);
			return (from Entity e in entities where e != null select e.GetEntityId()).ToList();
		}
		
		/// <summary>
		/// Set lazy load identifiers to many-to-many property.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <param name="relationProperty">
		/// Many-to-many relation property. <see cref="PropertyInfo"/>
		/// </param>
		/// <param name="values">
		/// Relation identifiers to be set.
		/// </param>
		internal static void SetLazyLoadIdentifiers(this Entity entity, PropertyInfo relationProperty, List<long?> values)
		{
			string propertyName = relationProperty.Name;
			string prefix = propertyName[0].ToString().ToLower() + propertyName.Substring(1, propertyName.Length - 1);
			string identifiersFieldName = prefix + "LazyLoadIdentifiers";
			entity.GetType().GetField(identifiersFieldName, BindingFlags.NonPublic | BindingFlags.Instance).SetValue(entity, values);
		}
		
		/// <summary>
		/// Get currently loaded entities of the specified to-many relation property. Returns list
		/// of entities that are currently in memory and linked to this entity.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <param name="toManyRelationProperty">
		/// To-Meny relation which loaded entities are returned. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// All related entities that are loaded into memory.<see cref="List<Entity>"/>
		/// </returns>
		internal static List<Entity> GetLoadedRelatedEntities(this Entity entity, PropertyInfo toManyRelationProperty)
		{
			string propertyName = toManyRelationProperty.Name;
			string fieldName = propertyName[0].ToString().ToLower() + propertyName.Substring(1, propertyName.Length - 1);
			IList entities = (IList) entity.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance).GetValue(entity);
			
			if(entities == null)
			{
				return new List<Entity>();
			}
			else
			{
				return entities.Cast<Entity>().ToList();
			}
		}
		
		/// <summary>
		/// Get currently loaded entity of the specified relation property.
		/// </summary>
		/// <param name="entity">
		/// Extended object. <see cref="Entity"/>
		/// </param>
		/// <param name="relationProperty">
		/// Relation which value is returned. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// Related entity of null if entity is not loaded from persistent store or doesn't exists at all.<see cref="Entity"/>
		/// </returns>
		internal static Entity GetLoadedRelatedEntity(this Entity entity, PropertyInfo relationProperty)
		{
			string propertyName = relationProperty.Name;
			string fieldName = propertyName[0].ToString().ToLower() + propertyName.Substring(1, propertyName.Length - 1);
			return (Entity) entity.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance).GetValue(entity);
		}
	}
}
